昨天有提到 service 會統合各項資源與應用,事實上並沒有明確的文章規範那些處理應該寫在 service 或是 controller,至少兩者之間所處理的事項小弟個人認為不如 repository 明確,而自己的習慣是將「不可分割的商務邏輯片段」視為一個單純的服務,會在 service 中獨立成一個 function。今天老樣子以 Post 為例。
namespace App\Services;
use Illuminate\Database\Eloquent\Model;
interface IService
{
// ...
public function update(Model $modelInstance, array $data);
// ...
}
<?php
namespace App\Services;
use App\Repositories\IRepository;
use Illuminate\Database\Eloquent\Model;
abstract class BaseService implements IService
{
protected $repository;
protected $resourceClass;
public function __construct(IRepository $repository, $resourceClass)
{
// 設定主要進行 CRUD 的 repository
$this->repository = $repository;
$this->resourceClass = $resourceClass;
}
// ...
public function update(Model $modelInstance, array $data)
{
// 我們將 IRepository 的 update function 改為可以接收
// Model 實例或是流水號 "id"
return $this->repository->update($modelInstance, $data);
}
public function modelToAPIResource($model)
{
return new $this->resourceClass($model);
}
// ...
}
最後依需求實作 PostService,結果如下。
首先更新文章情境為: 「系統收到文章更新需求與更新內容之後,需要先檢查更新者是管理者或作者本人,具有權限才得以更新文章,沒有權限就必須發送警告信給作者」。
情境描述中,更新一篇文章「檢查更新者權限」、「更新文章」和「發送警告信」,三個動作是不論何種情境都必須綁在一起,所以會建立包含三者的一個服務 (如 updatePost
)。
至於更新者 ID 取得方式、更新失敗後的其他處理,皆有可能會依照不同情境有所不同,因此上述項目就不會被包在 updatePost
中。
另外,我們也遵守將資料庫存取的邏輯撰寫於 repository,service 只進行整合與使用。同時,假設有各種不同更新 post 的情況,且未來權限檢查邏輯有所調整,我們只要修改 service 即可,呼叫 updatePost
的 controllers 就不需要逐一修改。
namespace App\Services;
use App\Http\Resources\Post;
use App\Repositories\PostRepository;
use App\Repositories\UserRepository;
use App\Utils\Messenger\EmailMessenger;
class PostService extends BaseService
{
private $emailMessenger;
private $userRepository;
// DI 注入各種會用到的 repositories 和其他類別實例
public function __construct(PostRepository $postRepository, UserRepository $userRepository, EmailMessenger $emailMessenger)
{
// 注入 BaseService 初始主要的 repository 和 resource
parent::__construct($postRepository, Post::class);
$this->userRepository = $userRepository;
$this->emailMessenger = $emailMessenger;
}
// 注意 PHP 沒有像 Java, C#, C++ 中的 overloading 寫法,所以方法名稱不能相同
public function updatePost($updatePersonId, $postId, array $data)
{
$updatePerson = $this->userRepository->readById($updatePersonId);
$post = $this->repository->readById($postId);
$isManager = $updatePerson && $updatePerson->isManager();
$isAuthor = $updatePerson && $post && $post->user_id === $updatePerson->id;
// 確認更新者權限
if ($isManager || $isAuthor) {
// 更新文章
$updatedPost = $this->update($data);
return $updatedPost;
} else {
// 寄出警告信
$this->emailMessenger
->setTargrt($post->user->email)
->setTitle("Post update warrning")
->setMessage("Someone({$updatePerson->id}) try to update your post '{$post->title}' without permission")
->send();
throw new \Exception("post update permission denied");
}
}
}
今天 service 在技術上相對簡單 (個人覺得至少比 DI 的理解簡單 XD),service 的困難雖不在技術而是在架構與設計。像小弟個人也覺得還有很多進步空間,但就是盡量符合 service 的概念、根據現有需求將各功能的耦合降低,讓日後維護或是撰寫測試 (其實測試應該是要最早寫才對)!
兩篇 controller 瘦身計畫之後,明天我們來看看瘦身後的 controller 吧!